<?php
/**
 * Author: yixuan
 * Desc: 微信公众号封装类
 * Time: 2016/5/18 14:08
 */

namespace app\lib;

use think\Cache;
use think\Db;

class WxoffiAccount
{
    private static $appid;
    private static $secret;
    private static $token;
    private static $encodingAesKey;

    static $instance = null;

    private function __construct()
    {
        //todo 设置微信公众号相关参数
        global $_G;
        if (!isset($_G['mpid'])) {
            return ['errcode' => 1, 'msg' => '平台参数缺失'];
        }
        //获取平台信息
        $mp_info = Db::table('yx_sys_platform')->where(['id' => $_G['mpid'], 'platform_type' => 2])->find();

        if (empty($mp_info) || is_null($mp_info)) {
            return ['errcode' => 1, 'msg' => '公众号信息获取失败'];
        }
        if (empty($mp_info['appid']) || empty($mp_info['secret'])) {
            return ['errcode' => 1, 'msg' => '公众号信息参数为空'];
        }
        self::$appid = $mp_info['appid'];
        self::$secret = $mp_info['secret'];
        self::$token = $mp_info['token'];
        self::$encodingAesKey = $mp_info['encodingaeskey'];
        //实例化jsconfig 判断全局的full_url
        if ($_G['url_full']) {
            $mp_info['jssdkconfig'] = $this->getJsConfig();
        }
        //挂载$_G['platform']
        $_G['platform'] = $mp_info;
    }

    public static function getInstance()
    {
        if (!self::$instance || is_null(self::$instance)) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    /**
     * Desc: 获取微信授权跳转地址
     * Author: yixuan
     * Time: 2016/5/18 14:46
     * @param $redirectUrl
     * @param $scope
     * @param $state
     * @return string
     */
    public function getAuthUrl($redirectUrl, $scope = 'snsapi_base', $state = 'YXSTATE', $force_popup = false)
    {
        $appid = self::$appid;
        $redirectUrl = urlencode($redirectUrl);
        //scope 默认为snsapi_base表示静默授权获取openid snsapi_userinfo 表示获取用户基本信息
        $url = "https://open.weixin.qq.com/connect/oauth2/authorize?appid={$appid}&redirect_uri={$redirectUrl}&response_type=code&scope={$scope}&state={$state}&forcePopup={$force_popup}#wechat_redirect";
        return $url;
    }

    /**
     * Desc:获取用户code换取用户accesstoken
     * Author: yixuan
     * Time: 2016/5/18 16:16
     * @param $code
     * @param $state
     * @return bool|mixed|string
     */
    public function getUserAccessToken($code)
    {
        if (!$code) {
            return ['errcode' => 1, 'msg' => '用户授权参数缺失,请重新授权'];
        }
        $appid = self::$appid;
        $secret = self::$secret;
        $tokenUrl = "https://api.weixin.qq.com/sns/oauth2/access_token?appid={$appid}&secret={$secret}&code={$code}&grant_type=authorization_code";
        $tokenRes = $this->requestApi($tokenUrl);
        if (!isset($tokenRes['access_token']) || !isset($tokenRes['openid'])) {
            return ['errcode' => 1, 'msg' => '获取用户授权失败,请重新尝试!'];
        }
        return $tokenRes;
    }

    /**
     * Desc:获取用户微信基本信息
     * Author: yixuan
     * Time: 2016/5/18 16:17
     * @param $accessToken
     * @param $openid
     * @return bool|mixed|string
     */
    public function getUserInfo($user_accessToken, $openid)
    {
        if (empty($openid)) {
            return ['errcode' => 1, 'msg' => 'openid参数缺失,获取用户信息失败!'];
        }
        if (empty($user_accessToken)) {
            return ['errcode' => 1, 'msg' => 'access_token参数缺失,获取用户信息失败!'];
        }
        $userInfoUrl = "https://api.weixin.qq.com/sns/userinfo?access_token={$user_accessToken}&openid={$openid}&lang=zh_CN";
        $userInfo = $this->requestApi($userInfoUrl);
        if (isset($userInfo['errcode'])) {
            return ['errcode' => 1, 'msg' => '获取用户信息失败,msg:!' . $userInfo['errcode']];
        }
        return $userInfo;
    }

    /**
     * Desc: 获取微信公众号access_token
     * Author: yixuan
     * Time: 2016/5/27 20:27
     * @return mixed
     */
    public function getAccessToken()
    {
        $appid = self::$appid;
        $secret = self::$secret;
        $tmp_system = ['appid' => $appid, 'secret' => $secret];
        $token = getAccessToken($tmp_system, true);
        //以下代码暂时注释掉 统一使用系统的获取公众号的access_token
//        $cache_key = 'wx_access_token_' . $appid;
//        if (!$token = Cache::get($cache_key)) {
//            $url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={$appid}&secret={$secret}";
//            $result = $this->requestApi($url);
//            if (isset($result['errcode']) && $result['errcode']) {
//                return ['errcode' => 1, 'msg' => '获取access_token失败,msg:' . $result['errcode']];
//            }
//            $token = $result['access_token'];
//            Cache::set($cache_key, $result['access_token'], $result['expires_in'] - 100);
//        }
        return $token;
    }

    /**
     * Desc: 获取微信公众号稳定版access_token 可本地缓存
     * Author: yixuan
     * @return mixed
     */
    public function getStableAccessToken()
    {
        $appid = self::$appid;
        $secret = self::$secret;
        $force_refresh = false;
        $cache_key = 'wx_stable_access_token_' . $appid;
        if (!$access_token = Cache::get($cache_key)) {
            $url = "https://api.weixin.qq.com/cgi-bin/stable_token";
            $data = [
                'grant_type' => 'client_credential',
                'appid' => $appid,
                'secret' => $secret,
                'force_refresh' => $force_refresh
            ];
            $result = $this->requestApi($url, $data, true);
            if (isset($result['errcode']) && $result['errcode']) {
                return ['errcode' => 1, 'msg' => '获取access_token失败,msg:' . $result['errcode']];
            }
            if (isset($result['access_token']) && isset($result['expires_in'])) {
                $access_token = $result['access_token'];
                Cache::set($cache_key, $result['access_token'], $result['expires_in'] - 100);
            }
        }
        $access_token;
    }

    /**
     * Desc:生成js-sdk页面需要的配置参数
     * Author: yixuan
     * Time: 2016/6/3 16:47
     * @return array
     */
    public function getJsConfig($url = '')
    {
        global $_G;
        $jsapi_ticket = $this->getJsapiTicket();
        $timestamp = time();
        $noncestr = $this->createNoncestr();
        $url = $url != '' ? $url : $_G['url_full'];
        if (strpos($url, '#') !== false) {
            $url = substr($url, 0, strpos($url, '#'));//签名url为剔除包含#之后的所有内容的url
        }
        $str = "jsapi_ticket={$jsapi_ticket}&noncestr={$noncestr}&timestamp={$timestamp}&url={$url}";
        $signature = sha1($str);
        return [
            'appId' => self::$appid,
            'timestamp' => $timestamp,
            'nonceStr' => $noncestr,
            'signature' => $signature
        ];
    }

    /**
     * Desc: 获取微信公众号jsapi_ticket
     * Author: yixuan
     * Time: 2016/5/27 20:27
     * @return mixed
     */
    public function getJsapiTicket()
    {
        $accessToken = $this->getAccessToken();
        if (isset($accessToken['errcode'])) {
            return ['errcode' => 1, 'msg' => $accessToken['msg']];
        }
        $cache_key = 'wx_jsapi_ticket_' . $accessToken;
        if (!$ticket = Cache::get($cache_key)) {
            $url = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token={$accessToken}&type=jsapi";
            $result = $this->requestApi($url);
            if (isset($result['errcode']) && $result['errcode']) {
                return ['errcode' => 1, 'msg' => $result['errcode']];
            }
            $ticket = $result['ticket'];
            Cache::set($cache_key, $result['ticket'], $result['expires_in'] - 100);
        }
        return $ticket;
    }

    /**
     * Desc:生成随机字符串
     * Author: yixuan
     * Time: 2016/6/3 16:40
     * @param int $length 随机字符串长度 默认16位
     * @return string
     */
    private function createNonceStr($length = 16)
    {
        $chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
        $str = "";
        for ($i = 0; $i < $length; $i++) {
            $str .= substr($chars, mt_rand(0, strlen($chars) - 1), 1);
        }
        return $str;
    }

    /**
     * Desc:公众号模板消息下发
     * Author: yixuan
     * Time: 2016/6/3 10:37
     * @param $data
     * @return array|void
     */
    public function sendTemplateMsg($data)
    {

        $accessToken = $this->getAccessToken();
        if (isset($accessToken['errcode'])) {
            return ['errcode' => 1, 'msg' => $accessToken['msg']];
        }
        $url = "https://api.weixin.qq.com/cgi-bin/message/template/send?access_token={$accessToken}";
        $res = $this->requestApi($url, $data, true);
        if (isset($res['errcode']) && $res['errcode']) {
            return ['errcode' => 1, 'msg' => $this->getErrorMsg($res['errcode'])];
        }
    }

    /**
     * Desc:微信签名验证
     * Author: yixuan
     * Time: 2016/6/3 10:37
     * @param $timestamp
     * @param $nonce
     * @param $signature
     * @return bool
     */
    public function checkSign($timestamp, $nonce, $signature)
    {
        $token = self::$token;
        $tmpArr = array($token, $timestamp, $nonce);
        sort($tmpArr, SORT_STRING);
        $tmpStr = implode($tmpArr);
        $tmpStr = sha1($tmpStr);
        return $tmpStr == $signature;
    }

    /**
     * Desc:微信用户关注公众号处理
     * Author: yixuan
     * Time: 2016/6/3 10:59
     * @param $data
     */
    public function handleSubscribe($data)
    {
        $openid = $data['FromUserName'];
        //todo 处理用户关注公众号后的逻辑
    }

    /**
     * Desc:微信用户取消关注公众号处理
     * Author: yixuan
     * Time: 2016/6/3 11:00
     * @param $data
     */
    public function handleUnsubscribe($data)
    {
        $openid = $data['FromUserName'];
        //todo 处理用户取消关注公众号后的逻辑
    }

    /**
     * Desc:微信用户扫码事件处理
     * Author: yixuan
     * Time: 2016/6/3 11:00
     * @param $data
     */
    public function handleScan($data)
    {
        $openid = $data['FromUserName'];
        $sceneId = $data['EventKey']; // 是一个32位无符号整数，即创建二维码时的二维码scene_id
        //todo 处理用户扫码后的逻辑
    }

    /**
     * Desc:生成临时带参数的二维码
     * Author: yixuan
     * Time: 2016/6/3 14:54
     * @param $sceneId
     * @return array|string
     */
    public function getTempQRCode($sceneId)
    {
        $url = "https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=" . $this->getAccessToken();
        $data = [
            'expire_seconds' => 2592000,
            'action_name' => 'QR_SCENE',
            'action_info' => [
                'scene' => [
                    'scene_id' => $sceneId,
                ],
            ],
        ];
        $jsonData = json_encode($data);
        $res = $this->requestApi($url, $jsonData, true);
        if (isset($res['ticket'])) {
            return 'https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=' . urlencode($res['ticket']);
        }
        return ['errcode' => 1, 'msg' => '获取二维码失败'];
    }

    /**
     * Desc:错误码匹配中文错误信息
     * Author: yixuan
     * Time: 2016/6/3 14:56
     * @param $errcode
     * @return string
     */
    public function getErrorMsg($errcode)
    {
        $error_arr = [
            '10003' => 'redirect_uri域名与后台配置不一致',
            '10004' => '此公众号被封禁',
            '10005' => '此公众号并没有这些scope的权限',
            '10009' => '操作太频繁了，请稍后重试',
            '10010' => 'scope不能为空',
            '10011' => 'redirect_uri不能为空',
            '10012' => 'appid不能为空',
            '10013' => 'state不能为空',
            '10015' => '公众号未授权第三方平台，请检查授权状态',
            '10016' => '公众号未授权第三方平台，请检查授权状态',
            '40029' => 'code已失效',
            '43116' => '该模板因滥用被滥用过多，已被限制下发',
            '40249' => '不支持下发营销/推广类的消息内容',
            '40250' => '下发消息内容不规范（包含空值等），建议检查内容规范性后再下发',
            '40251' => '因历史违规导致平台限制账号调用上限，当前已到达下发上限',
            '40252' => '正在调用的模板下发的部分内容已进入平台审核流程，在审核完成前，相关内容暂时无法下发',
        ];
        if (isset($error_arr[$errcode])) {
            return $error_arr[$errcode];
        }
        return '未知错误';
    }

    public function requestApi($url, $data = [], $isPost = false)
    {
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);

        if ($isPost) {
            curl_setopt($ch, CURLOPT_POST, 1);
            curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
            // 如果API要求JSON格式，则添加此头信息
            curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
        }

        $output = curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);

        if ($httpCode == 200) {
            return json_decode($output, true);
        } else {
            throw new Exception("请求微信API失败，HTTP状态码：{$httpCode}");
        }
    }

    /***********以下为业务侧方法**********/
    /**
     * Desc:模板消息-订单提醒
     * Author: yixuan
     * Time: 2016/6/3 14:56
     * @param $message
     * @param $system
     * @return array|null
     */
    public function sendTempNewOrder($message, $system)
    {
        $send_data = [
            'touser' => $message['openId'],
            'template_id' => $system['neworder'],
            'url' => $message['url']
        ];
        $tixing = explode('您', $message['first']);
        $tixing = str_replace(' ', '', $tixing[1]);
        $tixing = str_replace('总计', '共', $tixing);
        if (mb_strlen($tixing) > 20) {
            $tixing = explode(',', $tixing);
            $tixing = $tixing[0];
        }
        if (!empty($message['remark'])) {
            $message['remark'] = "【" . mb_substr($message['remark'], 0, 12, 'utf-8') . "】";
        }
        $send_data['data'] = array(
            'time6' => array(
                'value' => $message['tradeDateTime'],
            ),
            'thing4' => array(
                'value' => $tixing,
            ),
            'thing9' => array(
                'value' => $message['customerInfo'] . $message['remark'],
            ),
            'thing3' => array(
                'value' => $message['orderType'] . "【" . $message['orderItemName'] . "】",
            ),
            'phone_number10' => array(
                'value' => $message['orderItemData'],
            ),
        );
        return $this->sendTemplateMsg($send_data);
    }
}